;
(function($) {
    var escapeMap = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#x27;',
        '`': '&#x60;'
    };
    // Functions for escaping and unescaping strings to/from HTML interpolation.
    var createEscaper = function(map) {
        var escaper = function(match) {
            return map[match];
        };
        // Regexes for identifying a key that needs to be escaped.
        var source = '(?:' + Object.keys(map).join('|') + ')';
        var testRegexp = RegExp(source);
        var replaceRegexp = RegExp(source, 'g');
        return function(string) {
            string = string == null ? '' : '' + string;
            return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
        };
    };

    var htmlEscape = createEscaper(escapeMap);

    $.fn.selectpicker.defaults.keepInputsWhenNoResult = false;
    $.fn.selectpicker.Constructor.prototype.noResultFormater = function() {
        var that = this;
        var htmlstr = '\
            <li data-original-index="{originalIndex}" data-anchor-area="li">\
                <a tabindex="{tabindex}" class="active" style="" data-tokens="null" role="option" aria-disabled="false" aria-selected="false" data-anchor-area="a">\
                    <span class="text" data-anchor-area="textspan">{text}</span>\
                    <span class="glyphicon glyphicon-ok check-mark"></span>\
                </a>\
            </li>';
        return $(htmlstr);
    };

    $.fn.selectpicker.Constructor.prototype.createLi = function(){
      var that = this,
          _li = [],
          optID = 0,
          titleOption = document.createElement('option'),
          liIndex = -1; // increment liIndex whenever a new <li> element is created to ensure liObj is correct

      // Helper functions
      /**
       * @param content
       * @param [index]
       * @param [classes]
       * @param [optgroup]
       * @returns {string}
       */
      var generateLI = function(content, index, classes, optgroup,optObj) {
          var displayHtml = "";
          if(optObj!=null){
            if(optObj[0].className.indexOf("notInSearch")!=-1){
              displayHtml = "display:none";
            }
          }
          return '<li style="'+displayHtml+'"' +
              ((typeof classes !== 'undefined' & '' !== classes) ? ' class="' + classes + '"' : '') +
              ((typeof index !== 'undefined' & null !== index) ? ' data-original-index="' + index + '"' : '') +
              ((typeof optgroup !== 'undefined' & null !== optgroup) ? 'data-optgroup="' + optgroup + '"' : '') +
              '>' + content + '</li>';
      };

      /**
       * @param text
       * @param [classes]
       * @param [inline]
       * @param [tokens]
       * @returns {string}
       */
      var generateA = function(text, classes, inline, tokens) {
          return '<a tabindex="0"' +
              (typeof classes !== 'undefined' ? ' class="' + classes + '"' : '') +
              (typeof inline !== 'undefined' ? ' style="' + inline + '"' : '') +
              (that.options.liveSearchNormalize ? ' data-normalized-text="' + normalizeToBase(htmlEscape(text)) + '"' : '') +
              (typeof tokens !== 'undefined' || tokens !== null ? ' data-tokens="' + tokens + '"' : '') +
              ' role="option">' + text +
              '<span class="' + that.options.iconBase + ' ' + that.options.tickIcon + ' check-mark"></span>' +
              '</a>';
      };

      if (this.options.title && !this.multiple) {
          // this option doesn't create a new <li> element, but does add a new option, so liIndex is decreased
          // since liObj is recalculated on every refresh, liIndex needs to be decreased even if the titleOption is already appended
          liIndex--;

          if (!this.$element.find('.bs-title-option').length) {
              // Use native JS to prepend option (faster)
              var element = this.$element[0];
              titleOption.className = 'bs-title-option';
              titleOption.appendChild(document.createTextNode(this.options.title));
              titleOption.value = '';
              element.insertBefore(titleOption, element.firstChild);
              // Check if selected or data-selected attribute is already set on an option. If not, select the titleOption option.
              // the selected item may have been changed by user or programmatically before the bootstrap select plugin runs,
              // if so, the select will have the data-selected attribute
              var $opt = $(element.options[element.selectedIndex]);
              if ($opt.attr('selected') === undefined && this.$element.data('selected') === undefined) {
                  titleOption.selected = true;
              }
          }
      }

      this.$element.find('option').each(function(index) {
          var $this = $(this);

          liIndex++;

          if ($this.hasClass('bs-title-option')) return;

          // Get the class and text for the option
          var optionClass = this.className || '',
              inline = this.style.cssText,
              text = $this.data('content') ? $this.data('content') : $this.html(),
              tokens = $this.data('tokens') ? $this.data('tokens') : null,
              subtext = typeof $this.data('subtext') !== 'undefined' ? '<small class="text-muted">' + $this.data('subtext') + '</small>' : '',
              icon = typeof $this.data('icon') !== 'undefined' ? '<span class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></span> ' : '',
              $parent = $this.parent(),
              isOptgroup = $parent[0].tagName === 'OPTGROUP',
              isOptgroupDisabled = isOptgroup && $parent[0].disabled,
              isDisabled = this.disabled || isOptgroupDisabled;

          if (icon !== '' && isDisabled) {
              icon = '<span>' + icon + '</span>';
          }

          if (that.options.hideDisabled && (isDisabled && !isOptgroup || isOptgroupDisabled)) {
              liIndex--;
              return;
          }

          if (!$this.data('content')) {
              // Prepend any icon and append any subtext to the main text.
              text = icon + '<span class="text">' + text + subtext + '</span>';
          }

          if (isOptgroup && $this.data('divider') !== true) {
              if (that.options.hideDisabled && isDisabled) {
                  if ($parent.data('allOptionsDisabled') === undefined) {
                      var $options = $parent.children();
                      $parent.data('allOptionsDisabled', $options.filter(':disabled').length === $options.length);
                  }

                  if ($parent.data('allOptionsDisabled')) {
                      liIndex--;
                      return;
                  }
              }

              var optGroupClass = ' ' + $parent[0].className || '';

              if ($this.index() === 0) { // Is it the first option of the optgroup?
                  optID += 1;

                  // Get the opt group label
                  var label = $parent[0].label,
                      labelSubtext = typeof $parent.data('subtext') !== 'undefined' ? '<small class="text-muted">' + $parent.data('subtext') + '</small>' : '',
                      labelIcon = $parent.data('icon') ? '<span class="' + that.options.iconBase + ' ' + $parent.data('icon') + '"></span> ' : '';

                  label = labelIcon + '<span class="text">' + label + labelSubtext + '</span>';

                  if (index !== 0 && _li.length > 0) { // Is it NOT the first option of the select && are there elements in the dropdown?
                      liIndex++;
                      _li.push(generateLI('', null, 'divider', optID + 'div'));
                  }
                  liIndex++;
                  _li.push(generateLI(label, null, 'dropdown-header' + optGroupClass, optID));
              }

              if (that.options.hideDisabled && isDisabled) {
                  liIndex--;
                  return;
              }

              _li.push(generateLI(generateA(text, 'opt ' + optionClass + optGroupClass, inline, tokens), index, '', optID));
          } else if ($this.data('divider') === true) {
              _li.push(generateLI('', index, 'divider'));
          } else if ($this.data('hidden') === true) {
              _li.push(generateLI(generateA(text, optionClass, inline, tokens), index, 'hidden is-hidden'));
          } else {
              var showDivider = this.previousElementSibling && this.previousElementSibling.tagName === 'OPTGROUP';

              // if previous element is not an optgroup and hideDisabled is true
              if (!showDivider && that.options.hideDisabled) {
                  // get previous elements
                  var $prev = $(this).prevAll();

                  for (var i = 0; i < $prev.length; i++) {
                      // find the first element in the previous elements that is an optgroup
                      if ($prev[i].tagName === 'OPTGROUP') {
                          var optGroupDistance = 0;

                          // loop through the options in between the current option and the optgroup
                          // and check if they are hidden or disabled
                          for (var d = 0; d < i; d++) {
                              var prevOption = $prev[d];
                              if (prevOption.disabled || $(prevOption).data('hidden') === true) optGroupDistance++;
                          }

                          // if all of the options between the current option and the optgroup are hidden or disabled, show the divider
                          if (optGroupDistance === i) showDivider = true;

                          break;
                      }
                  }
              }

              if (showDivider) {
                  liIndex++;
                  _li.push(generateLI('', null, 'divider', optID + 'div'));
              }
              _li.push(generateLI(generateA(text, optionClass, inline, tokens), index,"","",$this));
          }

          that.liObj[index] = liIndex;
      });

      //If we are not multiple, we don't have a selected item, and we don't have a title, select the first element so something is set in the button
      if (!this.multiple && this.$element.find('option:selected').length === 0 && !this.options.title) {
          this.$element.find('option').eq(0).prop('selected', true).attr('selected', 'selected');
      }

      return _li.join('');
    }

    $.fn.selectpicker.Constructor.prototype.liveSearchListener = function() {
        var that = this;
        var keep = this.options.keepInputsWhenNoResult
        var $no_results = (keep === true) ? this.noResultFormater() : $('<li class="no-results"></li>');

        this.$button.on('click.dropdown.data-api', function() {
            that.$menuInner.find('.active').removeClass('active');
            if (!!that.$searchbox.val()) {
                that.$searchbox.val('');
                that.$lis.not('.is-hidden').removeClass('hidden');
                if (!!$no_results.parent().length) $no_results.remove();
            }
            if (!that.multiple) that.$menuInner.find('.selected').addClass('active');
            setTimeout(function() {
                that.$searchbox.focus();
            }, 10);
        });

        this.$searchbox.on('click.dropdown.data-api focus.dropdown.data-api touchend.dropdown.data-api', function(e) {
            e.stopPropagation();
        });

        this.$searchbox.on('input propertychange', function() {
            that.$lis.not('.is-hidden').removeClass('hidden');
            that.$lis.filter('.active').removeClass('active');
            $no_results.remove();

            if (that.$searchbox.val()) {
                var $searchBase = that.$lis.not('.is-hidden, .divider, .dropdown-header'),
                    $hideItems;
                if (that.options.liveSearchNormalize) {
                    $hideItems = $searchBase.find('a').not(':a' + that._searchStyle() + '("' + normalizeToBase(that.$searchbox.val()) + '")');
                } else {
                    $hideItems = $searchBase.find('a').not(':' + that._searchStyle() + '("' + that.$searchbox.val() + '")');
                }

                if ($hideItems.length === $searchBase.length) {
                    var inputVal = that.$searchbox.val();
                    if (keep) {
                        $no_results.find("[data-anchor-area='textspan']").text(inputVal + "");
                        $no_results.addClass("selected active noresult").attr("data-original-index", $hideItems.length + "");
                        $no_results.find("[data-anchor-area='a']").attr("tabindex", $hideItems.length + "");
                        that.$menuInner.append($no_results);
                        $no_results.off("click").on("click", function() {
                            var $this = $(this);
                            var index = $this.attr("data-original-index");
                            that.setSelected(index, true, $this);
                            that.$button.attr("title", inputVal)
                                .find("span.filter-option").text(inputVal);
                            that.$element.find(".noresult").remove();
                            that.$element.append("<option value='"+inputVal+"' class='noresult'>" + inputVal + "</option>");
                              that.$element.selectpicker("refresh").selectpicker("val",inputVal);
                              var ev = document.createEvent("HTMLEvents");
                              ev.initEvent("change", false, true);
                              that.$element.get(0).dispatchEvent(ev);
                            setTimeout(function() {
                                that.$button.removeClass("bs-placeholder");
                                that.$element.trigger("bootstrap.select.noresult.haskeeped", inputVal);
                            }, 0);
                        });
                    } else {
                        $no_results.html(that.options.noneResultsText.replace('{0}', '"' + htmlEscape(that.$searchbox.val()) + '"'));
                        that.$menuInner.append($no_results);
                        that.$element.trigger("bootstrap.select.hasresult.haskeeped", inputVal);
                    }
                    that.$lis.addClass('hidden');
                } else {
                    $hideItems.parent().addClass('hidden');

                    var $lisVisible = that.$lis.not('.hidden'),
                        $foundDiv;

                    // hide divider if first or last visible, or if followed by another divider
                    $lisVisible.each(function(index) {
                        var $this = $(this);

                        if ($this.hasClass('divider')) {
                            if ($foundDiv === undefined) {
                                $this.addClass('hidden');
                            } else {
                                if ($foundDiv) $foundDiv.addClass('hidden');
                                $foundDiv = $this;
                            }
                        } else if ($this.hasClass('dropdown-header') && $lisVisible.eq(index + 1).data('optgroup') !== $this.data('optgroup')) {
                            $this.addClass('hidden');
                        } else {
                            $foundDiv = null;
                        }
                    });
                    if ($foundDiv) $foundDiv.addClass('hidden');

                    $searchBase.not('.hidden').first().addClass('active');
                }
            }
        });
    }
})($);
